#!/user/bin/env python
# -*- coding: utf-8 -*-

__all__ = ['Cmdline']

import os
import sys
import errno
import getopt
import gettext
import traceback

import tatolib.taskobj   as taskobj
import tatolib.errors    as errors
import tatolib.strutil   as strutil 
import tatolib.timeutil  as timeutil 
import tatolib.sqlite3db as sqlite3db
import tatolib.config    as config

localedir = os.path.join(sys.prefix, 'share')
gettext.install('tato', localedir, unicode=True)

#
# Command line format:
#
#     tato <command> <target> [options]
#
#         command = new, update, delete, list
#         target  = tag, task
#
ERRMSG_INCOMPLETE_ARGS        = _("Error: Incomplete arguments")
ERRMSG_UNKNOWN_ARGS           = _("Error: Unknown arguments")
ERRMSG_UNKNOWN_COMMAND        = _("Error: Unknown command")
ERRMSG_UNKNOWN_TARGET         = _("Error: Unknown operation target")
ERRMSG_REQUIRED_ARGS_MISSING  = _("Error: Missing required arguments")
ERRMSG_BAD_FORMAT_ARGS        = _("Error: Bad format arguments")
ERRMSG_FAILED_TO_EXECUTE      = _("Error: Failed to execute")
ERRMSG_BAD_DBFILE_DEFAULT     = _("Error: Bad default database file")
ERRMSG_BAD_DBFILE_ENV         = _("Error: Bad database file from environment variable")
ERRMSG_BAD_DBFILE_BAD_PATH    = _("Error: Bad database file from environment variable, cannot create file path")
ERRMSG_BAD_DBCLASS            = _("Error: Bad database class (bug, check with developer)")
ERRMSG_NOT_SUPPORTED          = _("Error: This feature is not supported right now")
ERRMSG_UNDEFINED_ID           = _("Error: Given ID is not in use")
ERRMSG_OPT_CANNOT_SET_BOTH    = _("Error: Options cannot set at the same time")
ERRMSG_OBJECT_EXISTS          = _("Error: Object already exists")
ERRMSG_NO_EFFECT              = _("Error: Operation not performed")

ERRMSG_INTERNAL_UNKNOWN_ERROR = _("Error: Unknown exception")
ERRMSG_INTERNAL_UNKNOWN_OBJ   = _("Internal error: Unknown object")
ERRMSG_INTERNAL_WRONG_PARAMS  = _("Internal error: Incorrect parameters")

INFO_ON_TRACKING              = _("TRACKING")
INFO_COMPLETE                 = _("DONE")


MSG_LIST_INFO_TASK = _("ID\tWeighting\tName\tDue\tComplete\tFlags\tTags")
MSG_LIST_INFO_TAG =  _("ID\tWeighting\tName")
MSG_GET_TASK  = _("""
ID:        {0}
Name:      {1}
Weighting: {2}
Due:       {3}
Complete:  {4}
Flags:     {5}
Detail:
\t{6}
""")
MSG_GET_TAG  = _("""
ID:        {0}
Name:      {1}
Weighting: {2}
Detail:
\t{3}
""")

MSG_GETHELP = _("""
Usage:
{0} <command> <target> [options]

Available commands:

  new\tCreate a new object.
  list\tList objects added in database.
  update\tUpdate attributes for an object in database.
  delete\tDelete an object in database.
  assign\tAssign a tag to a task or unassign tag from a task.

Availble targets:

  task\tA task which represents a work item.
  tag\tA tag to be assigned to a task.

Available options:

  {0} new task 
      --name=|-n\tRequired. Name of the task.
      --detail=|-d\tOptional. Detail description of the task.
      --due=|-u\tRequired. Due day of a task.
      --flags=|-f\tOptional. Flags of a task.

  {0} new tag
      --name=|-n\tRequired. Name of the task.
      --detail=|-d\tOptional. Detail description of the task.
      --weighting=|-w\tOptional. Priority weighting of a tag.

  {0} list task
      -a|--all\tOptional. If set to yes, all completed tasks are listed.
      -i|--id=\tOptional. If set, then list information of one task.

  {0} list tag
      -i|--id=\tOptional. If set, then list information of one tag.

  {0} update task 
      --id=|-i\tRequired. ID of a task object.
      --name=|-n\tOptional. New name value.
      --detail=|-d\tOptional. New detail description.
      --due=|-u\tOptional. New due day.
      --complete|-c\tOptional. Mark task as complete.
      --flags=|-f\tOptional. Values of flags.

      NOTE: A completed task cannot be re-activated.

  {0} update tag
      --id=|-i\tRequired. ID of a tag object.
      --name=|-n\tOptional. New name value.
      --detail=|-d\tOptional. New detail description.
      --weighting=|-d\tOptional. New tag weighting.

  {0} assign task
      --taskid=|-t\tRequired. ID of a task object.
      --tagid=|-a\tRequired. ID of a tag object.
      --set|-s\tOptional. Set a new tag.  
      --unset|-u\tOptional. Unset specified tag.  

      NOTE: --set and --unset cannot be given at the same time.
      Meanwhile, if neither is given, the default action will be --set.

Data format:

  Datetime
    Options:\t--due
    Format:\t[YYYY]/<MM>/<DD>
    Example:\t--due=2010/7/10

  Flag (new task)
    Options:\t--flags
    Format:\t<String1>:[weighting1] <String2>:[weighting2]
    Example:\t--flags="BuyFlower:1 BuyRing:2"

  Flag (update task)
    Options:\t--flags
    Format:\t<+-!?><String1>:[weighting1] <+-!?><String2>:[weighting2]
    Example:\t--flags="+BuyFlower:1 -BuyRing ?BackHome !EndYourWork"
    Comment:
    \tOperator +: Add a new flag, with weighting
    \tOperator -: Remove a new flag
    \tOperator ?: Unset an existing flag 
    \tOperator !: Set an existing flag 

  Weighing
    Options:\t--weighting --flags
    Format:\tPositive integers.

""")

def _u(input):
    "Internal function. Convert given string to unicode"
    # TODO This function must be updated. It should read current system
    # encoding to determine the encoding we use. This function should
    # be used only when parsing strings from command line.
    return strutil.u_(input)

def get_flag_param(flagstr):
    "Parse flag name and weighting value from given parameter"
    flagname      = None
    flagweighting = None
    flag_keyval = flagstr.split(':')
    if len(flag_keyval) == 1:
        # No weighting specified, use default value (1)
        flagname = flag_keyval[0] 
        flagweighting = 1
    elif len(flag_keyval) == 2: 
        try:
            flagname = flag_keyval[0] 
            flagweighting = int(flag_keyval[1])
        except Exception:
            # Filter values like --flags="Val:abc"
            return None, none
    else:
        # Filter values like --flags="Val:1:1"
        return None, None
    if flagname == "":
        # Filter values like --flags=":1"
        return None, None
    return _u(flagname), flagweighting


class Cmdline(object):
    def __init__(self, cmdargs, dbclass = sqlite3db.Database):
        """
        Cmdline.__init__(self, cmdargs) -> None

        Create a new Cmdline object. It reads and parses the command
        line. 

        If given command line is not valid, the constructor won't
        fail, but the following run() method returns a non-zero return
        value.
        """

        OPTS_NEW_SHORT = { 'task': 'n:d:u:f:', 'tag':  'n:d:w:' }
        OPTS_NEW_LONG  = { \
                'task': ['name=', 'detail=', 'due=', 'flags='], \
                'tag':  ['name=', 'detail=', 'weighting='] }

        OPTS_LIST_SHORT = { 'task': 'ai:', 'tag': 'i:' }
        OPTS_LIST_LONG  = { 'task': [ 'all', 'id=' ], 'tag': [ 'id=' ] }

        OPTS_DELETE_SHORT = { 'task': 'i:', 'tag': 'n:i:' }
        OPTS_DELETE_LONG  = { \
                'task': ['name=', 'id='], \
                'tag': ['name=', 'id='] }

        OPTS_UPDATE_SHORT = { 'task': 'i:n:d:u:f:c', 'tag': 'i:n:d:w:' }
        OPTS_UPDATE_LONG  = { \
                'task': ['id=', 'name=', 'detail=', \
                         'due=', 'flags=', 'complete'],\
                'tag':  ['id=', 'name=', 'detail=', 'weighting='] }

        OPTS_ASSIGN_SHORT = { 'task': 't:a:s:u' }
        OPTS_ASSIGN_LONG  = { \
                'task': ['taskid=', 'tagid=', 'set', 'unset'] }

        self.__OPTS = { 'new':     (OPTS_NEW_SHORT, OPTS_NEW_LONG), \
                        'list':    (OPTS_LIST_SHORT, OPTS_LIST_LONG), \
                        'delete':  (OPTS_DELETE_SHORT, OPTS_DELETE_LONG), \
                        'update':  (OPTS_UPDATE_SHORT, OPTS_UPDATE_LONG), \
                        'assign':  (OPTS_ASSIGN_SHORT, OPTS_ASSIGN_LONG) }

        self.__OPT_PARSER = { \
                'new':     { 'task': self.__parseopt_new_task, \
                             'tag':  self.__parseopt_new_tag }, \
                'list':    { 'task': self.__parseopt_list_task, \
                             'tag':  self.__parseopt_list_tag, }, \
                'delete':  { 'task': self.__parseopt_delete_task, \
                             'tag':  self.__parseopt_delete_tag }, \
                'update':  { 'task': self.__parseopt_update_task, \
                             'tag':  self.__parseopt_update_tag }, \
                'assign':  { 'task': self.__parseopt_assign_task }
                }

        # All private member variables
        self.__progname     = cmdargs[0]
        self.__dbclass      = dbclass
        self.__errormsg     = None
        self.__errordetail  = None
        self.__action       = None
        self.__exitcode     = 0
        self.__params       = None
        self.__dbfile       = None
        self.__db           = None

        # Initialize basic environment
        if not self.__initenv():
            return

        # Parse command line arguments
        if len(cmdargs) == 1: # No options
            self.__errormsg = None 
            self.__action   = self.__printhelp
            self.__exitcode = 0
            return
        elif len(cmdargs) == 2:
            self.__errormsg  = ERRMSG_INCOMPLETE_ARGS
            self.__action    = self.__printhelp
            self.__exitcode  = errno.EINVAL
            return
        elif len(cmdargs) >= 3:
            self.__command = cmdargs[1].lower()
            self.__target  = cmdargs[2].lower()
            if not self.__OPTS.has_key(self.__command):
                self.__errormsg = ERRMSG_UNKNOWN_COMMAND
                self.__action   = self.__printhelp
                self.__exitcode = errno.EINVAL
                return
            elif not OPTS_NEW_SHORT.has_key(self.__target):
                self.__errormsg = ERRMSG_UNKNOWN_TARGET
                self.__action   = self.__printhelp
                self.__exitcode = errno.EINVAL
                return
            else:
                opts_only = cmdargs[3:]
                sopts = self.__OPTS[self.__command][0][self.__target]
                lopts  = self.__OPTS[self.__command][1][self.__target]
                try:
                    opts, args = getopt.getopt(opts_only, sopts, lopts)
                except getopt.GetoptError as e:
                    self.__errormsg    = ERRMSG_UNKNOWN_ARGS
                    self.__errordetail = e.opt
                    self.__action      = self.__printhelp
                    self.__exitcode    = errno.EINVAL
                    return
                # Try parse the rest of the options.
                handler = self.__OPT_PARSER[self.__command][self.__target]
                handler(opts, args)
        return

    def __del__(self):
        if self.__db is not None:
            self.__db.close()

    def run(self):
        """
        Cmdline.run(self) -> Exit code

        Execution command based on command line arguments.
        """
        try:
            if self.__errormsg is not None:
                # Something wrong happens.
                if self.__errordetail is not None:
                    print ("{0}: {1}".format(self.__errormsg, \
                                             self.__errordetail))
                else:
                    print (self.__errormsg)
            if self.__action is not None:
                if self.__action():
                    return self.__exitcode
                else:
                    if self.__errormsg is not None:
                        # TODO More error handling code here.
                        print ("{0}: {1}".format(self.__errormsg, \
                                                 self.__errordetail))
        except Exception:
            print (ERRMSG_FAILED_TO_EXECUTE)
            # We return an error code in the following line.
        return errno.EPERM

    def __printhelp(self):
        print (MSG_GETHELP.format(self.__progname))

    def __parseopt_new_tag(self, opts, args):
        n = None
        d = None
        w = None
        for each_opt in opts:
            if each_opt[0] == '-n' or each_opt[0] == '--name':
                n = each_opt[1]
            elif each_opt[0] == '-d' or each_opt[0] == '--detail':
                d = each_opt[1]
            elif each_opt[0] == '-w' or each_opt[0] == '--weighting':
                w = each_opt[1]
        if n is None or d is None or w is None:
            self.__errormsg    = ERRMSG_REQUIRED_ARGS_MISSING
            self.__action      = None
            self.__exitcode    = errno.EINVAL
            if n is None:
                self.__errordetail = '--name'
            elif d is None:
                self.__errordetail = '--detail'
            elif w is None:
                self.__errordetail = '--weighting'
            return
        n = _u(n)
        d = _u(d)
        try:
            w = int(w)
            if w < 0:
                raise ValueError('weighting < 0', w)
        except Exception:
            self.__errormsg    = ERRMSG_BAD_FORMAT_ARGS 
            self.__errordetail = "-w"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
            return
        self.__errormsg = None
        self.__action   = self.__newobj
        self.__params = (taskobj.Tag, n, d, w)
        self.__exitcode = 0
        return

    def __parseopt_new_task(self, opts, args):
        n = None
        d = None
        u = None
        f = None
        due_local = None
        for each_opt in opts:
            if each_opt[0] == '-n' or each_opt[0] == '--name':
                n = each_opt[1]
            elif each_opt[0] == '-d' or each_opt[0] == '--detail':
                d = each_opt[1]
            elif each_opt[0] == '-u' or each_opt[0] == '--due':
                u = each_opt[1]
            elif each_opt[0] == '-f' or each_opt[0] == '--flags':
                f = each_opt[1]
        # Verify parameters
        if n is None or d is None or u is None:
            self.__errormsg = ERRMSG_REQUIRED_ARGS_MISSING
            self.__action   = None
            self.__exitcode = errno.EINVAL
            if n is None:
                self.__errordetail = '-n'
            elif d is None:
                self.__errordetail = '-d'
            elif u is None:
                self.__errordetail = '-u'
            return
        due_local = timeutil.strtolday(u)
        if due_local is None:
            self.__errormsg     = ERRMSG_BAD_FORMAT_ARGS
            self.__errordetail  = '-u' 
            self.__action       = None
            self.__exitcode     = errno.EINVAL
            return
        # format: "flag1:weighting1 flag2:weighting2 ..." 
        flag_map = {}
        if f is not None:
            splitted_flags = f.split(' ')
            for each_flagpair in splitted_flags:
                if len(each_flagpair) == 0: # Skip empty string
                    continue
                flagname, flagweighting = get_flag_param(each_flagpair)
                if flagname is not None and flagweighting is not None:
                    flag_map[flagname] = (flagweighting, False)
                else:
                    self.__errormsg     = ERRMSG_BAD_FORMAT_ARGS
                    self.__errordetail  = '-f' 
                    self.__action       = None
                    self.__exitcode     = errno.EINVAL
                    return

        # Don't create any object here. Just pass parameter to __newobj
        n = _u(n)
        d = _u(d)
        due_utc = timeutil.ltou(due_local)
        if len(flag_map) == 0:
            self.__params = (taskobj.Task, n, d, due_utc, None)
        else:
            self.__params = (taskobj.Task, n, d, due_utc, flag_map)
        self.__errormsg = None
        self.__action   = self.__newobj
        self.__exitcode = 0
        return

    def __parseopt_list_tag(self, opts, args):
        # Right now we don't support any command line options for list tag.
        # Always list them all.
        objid = None
        for each_opt in opts:
            if each_opt[0] == '-i' or each_opt[0] == '--id':
                objid = each_opt[1]
        if objid is not None:
            try:
                objid = int(objid)
            except Exception:
                self.__errormsg    = ERRMSG_BAD_FORMAT_ARGS 
                self.__errordetail = "-i"
                self.__action      = None
                self.__params      = None
                self.__exitcode    = errno.EINVAL
                return
            self.__errormsg = None
            self.__action   = self.__getobj
            self.__params   = (taskobj.Tag, objid)
            self.__exitcode = 0
        else:
            self.__errormsg = None
            self.__action   = self.__listobj
            self.__params   = (taskobj.Tag, False)
            self.__exitcode = 0
        return

    def __parseopt_list_task(self, opts, args):
        list_all = None
        objid = None
        for each_opt in opts:
            if each_opt[0] == '-a' or each_opt[0] == '--all':
                list_all = True
            elif each_opt[0] == '-i' or each_opt[0] == '--id':
                objid = each_opt[1]
        if list_all is not None and objid is not None:
            self.__errormsg    = ERRMSG_OPT_CANNOT_SET_BOTH
            self.__errordetail = "-i/-a"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
        elif objid is not None:
            try:
                objid = int(objid)
            except Exception:
                self.__errormsg    = ERRMSG_BAD_FORMAT_ARGS 
                self.__errordetail = "-i"
                self.__action      = None
                self.__params      = None
                self.__exitcode    = errno.EINVAL
                return
        elif list_all is None:
            list_all = False

        if list_all is not None:
            self.__errormsg = None
            self.__action   = self.__listobj
            self.__params   = (taskobj.Task, not list_all)
            self.__exitcode = 0
        elif objid is not None:
            self.__errormsg = None
            self.__action   = self.__getobj
            self.__params   = (taskobj.Task, objid)
            self.__exitcode = 0
        return

    def __parseopt_delete_tag(self, opts, args):
        objid = None
        for each_opt in opts:
            if each_opt[0] == '-i' or each_opt == '--id':
                objid = each_opt[1]
        if objid is None:
            self.__errormsg    = ERRMSG_REQUIRED_ARGS_MISSING
            self.__errordetail = "-i"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
            return
        try:
            objid = int(objid)
        except Exception:
            self.__errormsg    = ERRMSG_BAD_FORMAT_ARGS 
            self.__errordetail = "-i"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
            return
        self.__errormsg    = None 
        self.__action      = self.__deleteobj
        self.__params      = (taskobj.Tag, objid)
        self.__exitcode    = errno.EINVAL
        return

    def __parseopt_delete_task(self, opts, args):
        objid = None
        for each_opt in opts:
            if each_opt[0] == '-i' or each_opt[0] == '--id':
                objid = each_opt[1]
        if objid is None:
            self.__errormsg    = ERRMSG_REQUIRED_ARGS_MISSING
            self.__errordetail = "-i"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
            return
        try:
            objid = int(objid)
        except Exception:
            self.__errormsg    = ERRMSG_BAD_FORMAT_ARGS 
            self.__errordetail = "-i"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
            return
        self.__errormsg    = None 
        self.__action      = self.__deleteobj
        self.__params      = (taskobj.Task, objid)
        self.__exitcode    = errno.EINVAL
        return

    def __parseopt_update_tag(self, opts, args):
        i = 0
        n = None
        d = None
        w = None
        for each_opt in opts:
            if each_opt[0] == '-n' or each_opt[0] == '--name':
                n = each_opt[1]
            elif each_opt[0] == '-d' or each_opt[0] == '--detail':
                d = each_opt[1]
            elif each_opt[0] == '-w' or each_opt[0] == '--weighting':
                w = each_opt[1]
            elif each_opt[0] == '-i' or each_opt[0] == '--id':
                i = each_opt[1]
        # Verify parameters
        if i is None:
            self.__errormsg    = ERRMSG_REQUIRED_ARGS_MISSING
            self.__errordetail = "-i"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
            return
        if n is not None:
            n = _u(n)
        if d is not None:
            d = _u(d)
        try:
            i = int(i)
        except Exception:
            self.__errormsg    = ERRMSG_BAD_FORMAT_ARGS 
            self.__errordetail = "-i"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
            return
        try:
            if w is not None:
                w = int(w)
                if w < 0:
                    raise ValueError('weighting < 0', w)
        except Exception:
            self.__errormsg    = ERRMSG_BAD_FORMAT_ARGS 
            self.__errordetail = "-w"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
            return

        self.__errormsg        = None
        self.__action          = self.__updateobj
        self.__params          = (taskobj.Tag, i, n, d, w)
        self.__exitcode        = 0
        return

    def __parseopt_update_task(self, opts, args):
        i = None
        n = None
        d = None
        u = None
        f = None
        c = None
        due_local = None
        due_utc   = None
        new_flag_map    = []
        delete_flag_map = []
        set_flag_map    = []
        unset_flag_map  = []
        for each_opt in opts:
            if each_opt[0] == '-i' or each_opt[0] == '--id':
                i = each_opt[1]
            elif each_opt[0] == '-n' or each_opt[0] == '--name':
                n = each_opt[1]
            elif each_opt[0] == '-d' or each_opt[0] == '--detail':
                d = each_opt[1]
            elif each_opt[0] == '-u' or each_opt[0] == '--due':
                u = each_opt[1]
            elif each_opt[0] == '-f' or each_opt[0] == '--flags':
                f = each_opt[1]
            elif each_opt[0] == '-c' or each_opt[0] == '--complete':
                c = timeutil.utctoday()
        # Verify parameters
        if i is None:
            self.__errormsg     = ERRMSG_REQUIRED_ARGS_MISSING
            self.__errordetail  = '-i' 
            self.__action       = None
            self.__exitcode     = errno.EINVAL
            return
        try:
            i = int(i)
        except Exception:
            self.__errormsg     = ERRMSG_BAD_FORMAT_ARGS
            self.__errordetail  = '-i {0}'.format(i) 
            self.__action       = None
            self.__exitcode     = errno.EINVAL
            return
        if n is not None:
            n = _u(n)
        if d is not None:
            d = _u(d)
        if u is not None:
            due_local = timeutil.strtolday(u)
            if due_local is None:
                self.__errormsg     = ERRMSG_BAD_FORMAT_ARGS
                self.__errordetail  = '-u' 
                self.__action       = None
                self.__exitcode     = errno.EINVAL
                return
            due_utc = timeutil.ltou(due_local)

        # format: "+flag1:weighting1 -flag2:weighting2 ..." 
        if f is not None:
            splitted_flags = f.split(' ')
            for each_flagpair in splitted_flags:
                if len(each_flagpair) == 0: # Skip empty string
                    continue
                each_op = each_flagpair[0]
                if each_op == '+':
                    # User is trying to add a flag
                    newflag, weighting = get_flag_param(each_flagpair[1:])
                    if newflag is None or weighting is None:
                        self.__errormsg    = ERRMSG_BAD_FORMAT_ARGS
                        self.__errordetail = each_flagpair
                        self.__action      = None
                        self.__exitcode    = errno.EINVAL
                        return
                    new_flag_map.append((newflag, weighting))
                elif each_op == '-':
                    # User is trying to remove a flag
                    each_keyval = each_flagpair[1:].split(':')
                    if len(each_keyval) != 1:
                        self.__errormsg     = ERRMSG_BAD_FORMAT_ARGS
                        self.__errordetail  = '-f {0}'.format(each_keyval)
                        self.__action       = None
                        self.__exitcode     = errno.EINVAL
                        return
                    existingflag = each_flagpair[1:]
                    delete_flag_map.append(existingflag)
                elif each_op == '!':
                    # User is trying to set a flag
                    each_keyval = each_flagpair[1:].split(':')
                    if len(each_keyval) != 1:
                        self.__errormsg     = ERRMSG_BAD_FORMAT_ARGS
                        self.__errordetail  = '-f {0}'.format(each_keyval)
                        self.__action       = None
                        self.__exitcode     = errno.EINVAL
                        return
                    setflag = each_flagpair[1:]
                    set_flag_map.append(setflag)
                elif each_op == '?':
                    # User is trying to unset a flag
                    each_keyval = each_flagpair[1:].split(':')
                    if len(each_keyval) != 1:
                        self.__errormsg     = ERRMSG_BAD_FORMAT_ARGS
                        self.__errordetail  = '-f {0}'.format(each_keyval)
                        self.__action       = None
                        self.__exitcode     = errno.EINVAL
                        return
                    unsetflag = each_flagpair[1:]
                    unset_flag_map.append(unsetflag)
                else:
                    self.__errormsg     = ERRMSG_BAD_FORMAT_ARGS
                    self.__errordetail  = '-f'
                    self.__action       = None
                    self.__exitcode     = errno.EINVAL
                    return

        if f is None:
            self.__params = (taskobj.Task, i, n, d, due_utc, c, None)
        else:
            self.__params = (taskobj.Task, i, n, d, due_utc, c, \
                             (new_flag_map, delete_flag_map, \
                             set_flag_map, unset_flag_map))
        self.__errormsg = None
        self.__action   = self.__updateobj
        self.__exitcode = 0
        return

    def __parseopt_assign_task(self, opts, args):
        taskid     = None
        tagid      = None
        set_tag    = None
        unset_tag  = None
        for each_opt in opts:
            if each_opt[0] == '-t' or each_opt[0] == '--taskid':
                taskid = each_opt[1]
            elif each_opt[0] == '-a' or each_opt[0] == '--tagid':
                tagid = each_opt[1]
            elif each_opt[0] == '-s' or each_opt[0] == '--set':
                set_tag = True
            elif each_opt[0] == '-u' or each_opt[0] == '--unset':
                unset_tag = True
        # Verify parameters
        if taskid is None:
            self.__errormsg    = ERRMSG_REQUIRED_ARGS_MISSING
            self.__errordetail = "-t"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
            return
        if tagid is None:
            self.__errormsg    = ERRMSG_REQUIRED_ARGS_MISSING
            self.__errordetail = "-a"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
            return
        if set_tag is True and unset_tag is True:
            self.__errormsg    = ERRMSG_OPT_CANNOT_SET_BOTH
            self.__errordetail = "-a, -t"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
            return
        elif set_tag is None and unset_tag is None:
            set_tag = True # If nothing is set, assign tag by default
        elif set_tag is None and unset_tag is True:
            set_tag = False
        elif set_tag is True and unset_tag is None:
            set_tag = True
        try:
            taskid = int(taskid)
        except Exception:
            self.__errormsg    = ERRMSG_BAD_FORMAT_ARGS 
            self.__errordetail = "-t"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
            return
        try:
            tagid = int(tagid)
        except Exception:
            self.__errormsg    = ERRMSG_BAD_FORMAT_ARGS 
            self.__errordetail = "-a"
            self.__action      = None
            self.__params      = None
            self.__exitcode    = errno.EINVAL
            return

        self.__errormsg = None
        self.__action   = self.__assignobj
        self.__params   = (taskobj.Task, taskid, tagid, set_tag)
        self.__exitcode = 0
        return

    def __new_task(self):
        objcls = self.__params[0]
        if len(self.__params) != 5:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        try:
            n = self.__params[1]
            d = self.__params[2]
            u = self.__params[3]
            f = self.__params[4]
            # Only UTC is allowed
            u = timeutil.ltou(u)
            if f is not None:
                task = taskobj.Task(name = n, detail = d, due = u,\
                                    flags = f)
            else:
                task = taskobj.Task(name = n, detail = d, due = u)

            self.__db.new(task)
        except Exception:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True

    def __new_tag(self):
        objcls = self.__params[0]
        if len(self.__params) != 4:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        try:
            n = self.__params[1]
            d = self.__params[2]
            w = self.__params[3]
            tag = taskobj.Tag(name = n, detail = d, weighting = w)

            self.__db.new(tag)
            self.__errormsg    = None
            self.__errordetail = None 
            self.__exitcode    = 0 
        except errors.TaskObjectError as e:
            if e.error == errors.E_TA_SAMENAME:
                self.__errormsg    = ERRMSG_OBJECT_EXISTS
            elif e.error == errors.E_TA_ATTRMISSING:
                self.__errormsg    = ERRMSG_INCOMPLETE_ARGS
            elif e.error == errors.E_TA_BADVALUE:
                self.__errormsg    = ERRMSG_BAD_FORMAT_ARGS
            else:
                self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = n
            self.__exitcode    = errno.EINVAL 
            return False
        except Exception:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True


    def __newobj(self):
        objcls = self.__params[0]
        if objcls is taskobj.Task:
            return self.__new_task()
        elif objcls is taskobj.Tag:
            return self.__new_tag()
        else:
            self.__errormsg    = ERRMSG_INTERNAL_UNKNOWN_OBJ
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True

    def __get_task(self):
        objcls = self.__params[0]
        objid = self.__params[1]
        try:
            task = self.__db.getbyid(objcls, objid)
            if task is None:
                # Write nothing if task is not found.
                return True
            tags = []
            tag_ids = self.__db.gettagids(objid)
            for each_tagid in tag_ids:
                tag = self.__db.getbyid(taskobj.Tag, each_tagid)
                assert tag is not None
                tags.append(tag)

            i = objid
            n = task.name
            w = task.getweighting(tags)
            u = timeutil.daytostr(timeutil.utol(task.due))
            c = task.complete
            if c is None:
                c = INFO_ON_TRACKING
            else:
                c = _u("[{0} {1}]").format(INFO_COMPLETE,
                        timeutil.daytostr(timeutil.utol(c)))
            f = task.flagstostr()
            d = task.detail
            print (MSG_GET_TASK.format(i, n, w, u, c, f, d))
        except Exception as e:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True

    def __get_tag(self):
        objcls = self.__params[0]
        objid = self.__params[1]
        try:
            tag = self.__db.getbyid(objcls, objid)
            if tag is None:
                # Write nothing if tag is not found.
                return True
            i = objid
            n = tag.name
            w = tag.weighting
            d = tag.detail
            print (MSG_GET_TAG.format(i, n, w, d))
        except Exception:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True

    def __getobj(self):
        objcls = self.__params[0]
        if len(self.__params) != 2:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        if objcls is taskobj.Task:
            return self.__get_task()
        elif objcls is taskobj.Tag:
            return self.__get_tag()
        else:
            self.__errormsg    = ERRMSG_INTERNAL_UNKNOWN_OBJ
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True

    def __list_task(self):
        objcls = self.__params[0]
        list_complete = self.__params[1]
        try:
            objs = self.__db.list(objcls, list_complete)
            # Key step: sort all tasks by weighting
            objs.sort(key=lambda ele: int(ele[2]), reverse=True)

            # Write task list
            for each_obj in objs:
                taskid    = each_obj[0]
                task      = each_obj[1]
                weighting = each_obj[2]
                duestr = timeutil.daytostr(timeutil.utol(task.due))
                compstr = None

                if task.complete is not None:
                    compstr = _u("[{0} {1}]").format(INFO_COMPLETE,
                                   timeutil.daytostr(\
                                        timeutil.utol(task.complete)))
                else:
                    compstr = INFO_ON_TRACKING
                tagids =self.__db.gettagids(taskid)
                tagnames = []
                for each_tagid in tagids:
                    tag = self.__db.getbyid(taskobj.Tag, each_tagid)
                    tagnames.append(tag.name)
                tagstr = ";".join(tagnames)
                flagstr = task.flagstostr()
                info = _u("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}").format(\
                        taskid, weighting, task.name, \
                        duestr, compstr, \
                        flagstr, tagstr)
                print (info)
        except Exception:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True

    def __list_tag(self):
        objcls = self.__params[0]
        try:
            objs = self.__db.list(objcls)
            for each_obj in objs:
                tagid     = each_obj[0]
                tag       = each_obj[1]
                weighting = each_obj[2]
                info = _u("{0}\t{1}\t{2}").format(\
                        tagid, tag.weighting, tag.name)
                print (info)
        except Exception:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True


    def __listobj(self):
        objcls = self.__params[0]
        if len(self.__params) != 2:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        if objcls is taskobj.Task:
            return self.__list_task()
        elif objcls is taskobj.Tag:
            return self.__list_tag()
        else:
            self.__errormsg    = ERRMSG_INTERNAL_UNKNOWN_OBJ
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True

    def __update_task(self):
        objcls = self.__params[0]
        if len(self.__params) != 7:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        i = self.__params[1]
        n = self.__params[2]
        d = self.__params[3]
        u = self.__params[4]
        c = self.__params[5]
        flag_add_remove_list = self.__params[6]
        try:
            task = self.__db.getbyid(objcls, i)
            if task is None:
                self.__errormsg    = ERRMSG_OBJECT_NOT_FOUND
                self.__errordetail = i
                self.__exitcode    = errno.EINVAL
                return False
            if n is not None:
                task.name = n
            if d is not None:
                task.detail = d
            if u is not None:
                task.due = u
            if c is not None:
                task.complete = c
            # Rearrange flags

            if flag_add_remove_list is not None:
                # Update flags
                new_flags    = flag_add_remove_list[0]
                delete_flags = flag_add_remove_list[1]
                set_flags    = flag_add_remove_list[2]
                unset_flags  = flag_add_remove_list[3]
                # To simplify the error handling, we ignore the
                # undefined flags.
                # The following code may not be intuitive
                # because if does not following the operation
                # sequence in command line.a If may be changed
                # in the future.

                # 1. Delete flags
                for each_delf in delete_flags:
                    try:
                        each_delf = _u(each_delf)
                        task.delflag(each_delf)
                    except Exception:
                        self.__errormsg    = ERRMSG_NO_EFFECT
                        self.__errordetail = each_delf 
                        self.__exitcode    = errno.EINVAL
                        return False
                # 2. Add flags
                for each_addf in new_flags:
                    assert 2 == len(each_addf)
                    try:
                        addf_name = _u(each_addf[0])
                        task.addflag(addf_name, each_addf[1])
                    except Exception:
                        self.__errormsg    = ERRMSG_NO_EFFECT
                        self.__errordetail = each_addf[0]
                        self.__exitcode    = errno.EINVAL
                        return False
                # 3. Unset flags
                for each_unsetf in unset_flags:
                    try:
                        each_unsetf = _u(each_unsetf)
                        task.setflag(each_unsetf, False)
                    except Exception:
                        self.__errormsg    = ERRMSG_NO_EFFECT
                        self.__errordetail = each_unsetf
                        self.__exitcode    = errno.EINVAL
                        return False
                # 4. Set flags
                for each_setf in set_flags:
                    try:
                        each_setf = _u(each_setf)
                        task.setflag(each_setf)
                    except Exception:
                        self.__errormsg    = ERRMSG_NO_EFFECT
                        self.__errordetail = each_setf 
                        self.__exitcode    = errno.EINVAL
                        return False
            self.__db.update(i, task)
        except Exception:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True

    def __update_tag(self):
        objcls = self.__params[0]
        if len(self.__params) != 5:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        i = self.__params[1]
        n = self.__params[2]
        d = self.__params[3]
        w = self.__params[4]
        try:
            tag = self.__db.getbyid(objcls, i)
            if tag is None:
                self.__errormsg    = ERRMSG_OBJECT_NOT_FOUND
                self.__errordetail = i
                self.__exitcode    = errno.EINVAL
                return False
            else:
                if n is not None:
                    tag.name = n
                if d is not None:
                    tag.detail = d
                if w is not None:
                    tag.weighting = w
                self.__db.update(i, tag)
        except Exception as e:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True

    def __updateobj(self):
        objcls = self.__params[0]
        if objcls is taskobj.Task:
            return self.__update_task()
        elif objcls is taskobj.Tag:
            return self.__update_tag()
        else:
            self.__errormsg    = ERRMSG_INTERNAL_UNKNOWN_OBJ
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True

    def __deleteobj(self):
        objcls = self.__params[0]
        if len(self.__params) != 2:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        objid = self.__params[1]
        if objcls is taskobj.Task or objcls is taskobj.Tag:
            try:
                obj = self.__db.delete(objcls, objid) 
                # No output if object is not found.
            except Exception:
                self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
                self.__errordetail = str(objcls) 
                self.__exitcode    = errno.EINVAL 
                return False
        else:
            self.__errormsg    = ERRMSG_INTERNAL_UNKNOWN_OBJ
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True

    def __assignobj(self):
        objcls = self.__params[0]
        if objcls is not taskobj.Task:
            self.__errormsg    = ERRMSG_INTERNAL_UNKNOWN_OBJ
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        # Only task is allowed.
        if len(self.__params) != 4:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        taskid    = self.__params[1]
        tagid     = self.__params[2]
        set_tag   = self.__params[3]

        task = self.__db.getbyid(taskobj.Task, taskid)
        tag  = self.__db.getbyid(taskobj.Tag, tagid)
        if task is None:
            self.__errormsg    = ERRMSG_OBJECT_NOT_FOUND
            self.__errordetail = taskid
            self.__exitcode    = errno.EINVAL
            return False
        if tag is None:
            self.__errormsg    = ERRMSG_OBJECT_NOT_FOUND
            self.__errordetail = tagid
            self.__exitcode    = errno.EINVAL
            return False
        try:
            found_tagid = self.__db.assigntag(taskid, tagid, set_tag)
        except Exception:
            self.__errormsg    = ERRMSG_INTERNAL_WRONG_PARAMS
            self.__errordetail = str(objcls) 
            self.__exitcode    = errno.EINVAL 
            return False
        return True

    def __initenv(self):
        try:
            conf = config.Config()
        except errors.DatabaseError as e:
            if e.error == errors.E_DB_WRONGPATH:
                self.__errormsg    = ERRMSG_BAD_DBFILE_BAD_PATH
                self.__errordetail = config.ENVVAR_DBFILE
                self.__action      = None
                self.__exitcode    = errno.EINVAL
                return False
            elif e.error == errors.E_DB_NOTFILE:
                self.__errormsg    = ERRMSG_BAD_DBFILE_DEFAULT
                self.__errordetail = config.ENVVAR_DBFILE
                self.__action      = None
                self.__exitcode    = errno.EINVAL
                return False
            else:
                self.__errormsg    = ERRMSG_BAD_DBFILE_DEFAULT
                self.__errordetail = config.ENVVAR_DBFILE
                self.__action      = None
                self.__exitcode    = errno.EINVAL
                return False
        except Exception:
            self.__errormsg    = ERRMSG_INTERNAL_UNKNOWN_ERROR
            self.__errordetail = traceback.format_exc()
            self.__action      = None
            self.__exitcode    = errno.EINVAL
            return False

        # Create debugger flag if required
        if conf.debug:
            self.__log = errors.DoNothingLogger()
        else:
            self.__log = errors.StdoutLogger()

        # Now we can load the database (open existing or create a new
        # one)
        try:
            db = self.__dbclass(conf.dbfile)
        except TypeError:
            self.__log.err(traceback.format_exc())
            self.__errormsg    = ERRMSG_BAD_DBCLASS 
            self.__errordetail = type(self.__dbclass)
            self.__action      = None
            self.__exitcode    = errno.EINVAL
            return False
        except Exception:
            self.__log.err(traceback.format_exc())
            self.__errormsg    = ERRMSG_BAD_DBFILE_DEFAULT
            self.__errordetail = conf.dbfile
            self.__action      = None
            self.__exitcode    = errno.EINVAL
            return False

        self.__dbfile = conf.dbfile
        self.__db     = db
        return True

# vim:tw=78:et:sw=4
